import { ChildProcess, ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from "child_process";
import { CancellationToken, Uri } from "vscode";
import { IDisposable } from "../types";
import { Observable } from 'rxjs';
import { EQEnvironment, InterpreterInformation } from "../../eqEnvironments/info";
import { EQExecInfo } from "../../eqEnvironments/exec";

export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean };
export type ShellExecFunc = (command: string, options?: ShellOptions | undefined) => Promise<ExecutionResult<string>>;
export const IProcessServiceFactory = Symbol('IProcessServiceFactory');

export type SpawnOptions = ChildProcessSpawnOptions & {
    encoding?: string;
    token?: CancellationToken;
    mergeStdOutErr?: boolean;
    throwOnStdErr?: boolean;
    extraVariables?: NodeJS.ProcessEnv;
};

export type Output<T extends string | Buffer> = {
    source: 'stdout' | 'stderr';
    out: T;
};

export type ObservableExecutionResult<T extends string | Buffer> = {
    proc: ChildProcess | undefined;
    out: Observable<Output<T>>;
    dispose(): void;
};

export type ExecutionResult<T extends string | Buffer> = {
    stdout: T;
    stderr?: T;
};

export interface IProcessService extends IDisposable {
    execObservable(file: string, args: string[], options?: SpawnOptions): ObservableExecutionResult<string>;
    exec(file: string, args: string[], options?: SpawnOptions): Promise<ExecutionResult<string>>;
    shellExec(command: string, options?: ShellOptions): Promise<ExecutionResult<string>>;
    on(event: 'exec', listener: (file: string, args: string[], options?: SpawnOptions) => void): this;
}

export interface IProcessServiceFactory {
    create(resource?: Uri): Promise<IProcessService>;
}

export interface IEQExecutionService {
    getInterpreterInformation(): Promise<InterpreterInformation | undefined>;
    getExecutablePath(): Promise<string>;
    isModuleInstalled(moduleName: string): Promise<boolean>;
    getModuleVersion(moduleName: string): Promise<string | undefined>;
    getExecutionInfo(eqArgs?: string[]): EQExecInfo;

    execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult<string>;
    execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult<string>;

    exec(args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
    execModule(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
    execForLinter(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
}

export type ExecutionFactoryCreationOptions = {
    resource?: Uri;
    eqPath?: string;
};
export type ExecutionFactoryCreateWithEnvironmentOptions = {
    resource?: Uri;
    interpreter?: EQEnvironment;
    allowEnvironmentFetchExceptions?: boolean;
    /**
     * Ignore running `conda run` when running code.
     * It is known to fail in certain scenarios. Where necessary we might want to bypass this.
     *
     * @type {boolean}
     */
};

export const IEQExecutionFactory = Symbol('IEQExecutionFactory');
export interface IEQExecutionFactory {
    create(options: ExecutionFactoryCreationOptions): Promise<IEQExecutionService>;
    createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise<IEQExecutionService>;
    createCondaExecutionService(
        eqPath: string,
        processService: IProcessService,
    ): Promise<IEQExecutionService | undefined>;
}

export const IProcessLogger = Symbol('IProcessLogger');
export interface IProcessLogger {
    logProcess(fileOrCommand: string, args?: string[], options?: SpawnOptions): void;
}

export const IBufferDecoder = Symbol('IBufferDecoder');
export interface IBufferDecoder {
    decode(buffers: Buffer[], encoding: string): string;
}